<html><head>
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="-1" />
<link rel="stylesheet" href="test_page.css">
<script>
// Do a deep comparison of two values. Return true if their values are
// identical, false otherwise.
function deepCompare(left, right) {
  if (typeof(left) !== typeof(right))
    return false;
  // If their identity is the same or they're basic types with the same value,
  // they are equal.
  if (left === right)
    return true;
  // If it's a basic type and we got here, we know they're not equal.
  if (["undefined", "boolean", "number", "string", "function"].indexOf(
          typeof(left)) > -1) {
    return false;
  }
  // Use right_keys as a set containing all keys from |right| which we haven't
  // yet compared.
  var right_keys = {};
  for (var key in right)
    right_keys[key] = true;
  for (var key in left) {
    if (key in right_keys) {
       if (!deepCompare(left[key], right[key]))
        return false;
    } else {
      // |left| had a key that |right| didn't.
      return false;
    }
    delete right_keys[key];
  }
  // If there are keys left in |right_keys|, it means they didn't exist in
  // |left|, so the objects aren't equal.
  if (Object.keys(right_keys).length > 0)
    return false;
  return true;
}

function AdjustHeight(frameWin) {
  var div = frameWin.document.getElementsByTagName("div")[0];
  var height = frameWin.getComputedStyle(div).height;
  frameWin.frameElement.style.height = height;
}

// Called when the tests are completed. |result| should be "PASS" if the test(s)
// passed, or information about the failure if the test(s) did not pass.
function DidExecuteTests(result) {
  var plugin = document.getElementById("plugin");
  if (plugin.parentNode.removePlugin) {
    plugin.parentNode.removeChild(plugin);
    plugin = undefined;
  }
  if (CheckPostConditions())
    sendAutomationMessage(result);

  if (window == top)
    return;

  // Otherwise, we are in a subframe, so we can use this opportunity to resize
  // ourselves.  This can only be done if the parent frame has the same origin.
  if (window.frameElement)
    AdjustHeight(window);
}

function AppendFrame(testcase, i) {
  var p = document.createElement("P");
  p.setAttribute("class", "frame-container");

  var title = document.createElement("H2");
  title.appendChild(document.createTextNode(testcase));
  p.appendChild(title);

  var frame = document.createElement("IFRAME");
  var mode = ExtractSearchParameter("mode");
  var websocket_host = ExtractSearchParameter("websocket_host");
  var websocket_port = ExtractSearchParameter("websocket_port");
  var ssl_server_port = ExtractSearchParameter("ssl_server_port");
  var src = "?testcase=" + testcase;
  if (mode == "nacl")
    src += "&mode=nacl";
  if (websocket_host != "")
    src += "&websocket_host=" + websocket_host;
  if (websocket_port != "")
    src += "&websocket_port=" + websocket_port;
  if (ssl_server_port != "")
    src += "&ssl_server_port=" + ssl_server_port;
  frame.setAttribute("src", src);

  frame.setAttribute("onload", "LoadNext(" + (i + 1) + ")");
  p.appendChild(frame);

  document.body.appendChild(p);
}

function LoadNext(i) {
  var links = document.links;
  if (links.length > i)
    AppendFrame(links[i].firstChild.nodeValue, i);
}

function RunAll() {
  // Remove any existing frames.
  var existing = document.getElementsByClassName("frame-container");
  while (existing.length)
    existing[0].parentNode.removeChild(existing[0]);

  // Add new frames for each test, but do so one frame at a time.
  LoadNext(0);
}

function ExtractSearchParameter(name) {
  var nameIndex = location.search.indexOf(name + "=");
  if (nameIndex != -1) {
    var value = location.search.substring(nameIndex + name.length + 1);
    var endIndex = value.indexOf("&");
    if (endIndex != -1)
      value = value.substring(0, endIndex);
    return value;
  }
  return "";
}

// Parses the message, looking for strings of the form:
// TESTING_MESSAGE:<message_type>:<message_contents>
//
// If the message_data is not a string or does not match the above format, then
// undefined is returned.
//
// Otherwise, returns an array containing 2 items. The 0th element is the
// message_type, one of:
//  - AddPostCondition
//  - ClearConsole
//  - DidExecuteTests
//  - EvalScript
//  - LogHTML
//  - RemovePluginWhenFinished
//  - ReportProgress
// The second item is the verbatim message_contents.
function ParseTestingMessage(message_data) {
  if (typeof(message_data) != "string")
    return undefined;
  var testing_message_prefix = "TESTING_MESSAGE";
  var delim_str = ":";
  var delim1 = message_data.indexOf(delim_str);
  if (message_data.substring(0, delim1) !== testing_message_prefix)
    return undefined;
  var delim2 = message_data.indexOf(delim_str, delim1 + 1);
  if (delim2 == -1)
    delim2 = message_data.length;
  var message_type = message_data.substring(delim1 + 1, delim2);
  var message_contents = message_data.substring(delim2 + 1);
  return [message_type, message_contents];
}

function ClearConsole() {
  window.document.getElementById("console").innerHTML = "";
}

function LogHTML(html) {
  window.document.getElementById("console").innerHTML += html;
}

function RemovePluginWhenFinished() {
  window.document.getElementById("container").removePlugin = true;
}

function sendAutomationMessage(msg) {
  if (window.domAutomationController)
    window.domAutomationController.send(msg);
}

function LogTestTime(test_time) {
  console.log(test_time);
}

// If something goes really wrong, the test running inside the plugin may not
// terminate.  For example, if the plugin does not load, the test will never
// send "PASS" to the browser.  In this case we should explicitly use the
// automation controller to terminate the test.
function InternalError(msg) {
  LogHTML("<p>" + msg);
  sendAutomationMessage(msg);
}

function EvalScript(script) {
  try {
    eval(script);
  } catch(ex) {
  }
}

var conditions = [];
// Add a "PostCondition". These are bits of script that are run after the plugin
// is destroyed. If they evaluate to false or throw an exception, it's
// considered a failure.
function AddPostCondition(script) {
  conditions.push(script);
}
// Update the HTML to show the failure and update cookies so that ui_tests
// doesn't count this as a pass.
function ConditionFailed(error) {
  error_string = "Post condition check failed: " + error;
  InternalError(error_string);
}
// Iterate through the post conditions defined in |conditions| and check that
// they all pass.
function CheckPostConditions() {
  var success = true;
  for (var i = 0; i < conditions.length; ++i) {
    var script = conditions[i];
    try {
      if (!eval(script)) {
        ConditionFailed("\"" + script + "\"");
        success = false;
      }
    } catch (ex) {
      ConditionFailed("\"" + script + "\"" + " failed with exception: " +
                      "\"" + ex.toString() + "\"");
      success = false;
    }
  }
  return success;
}

function IsTestingMessage(message_data) {
  return (ParseTestingMessage(message_data) != undefined);
}

function handleTestingMessage(message_event) {
  var type_contents_tuple = ParseTestingMessage(message_event.data);
  if (type_contents_tuple) {
    var type = type_contents_tuple[0];
    var contents = type_contents_tuple[1];
    if (type === "AddPostCondition")
      AddPostCondition(contents);
    else if (type === "ClearConsole")
      ClearConsole();
    else if (type === "DidExecuteTests")
      DidExecuteTests(contents);
    else if (type === "EvalScript")
      EvalScript(contents);
    else if (type === "LogHTML")
      LogHTML(contents);
    else if (type === "RemovePluginWhenFinished")
      RemovePluginWhenFinished();
    else if (type === "ReportProgress")
      sendAutomationMessage(contents);
    else if (type === "LogTestTime")
      LogTestTime(contents);
  }
}

function sendProgress() {
  // We send "..." to signal that we're still working. See
  // ppapi/tests/testing_instance.h for how this works.
  sendAutomationMessage("...");
}

onload = function() {
  var testcase = ExtractSearchParameter("testcase");
  var mode = ExtractSearchParameter("mode");
  document.title = 'Test ' + testcase;
  var obj;
  if (mode == "nacl_newlib") {
    obj = document.createElement("EMBED");
    obj.setAttribute("src", "ppapi_nacl_tests_newlib.nmf");
    obj.setAttribute("type", "application/x-nacl");
    obj.setAttribute("mode", mode);
  } else if (mode == "nacl_glibc") {
    obj = document.createElement("EMBED");
    obj.setAttribute("src", "ppapi_nacl_tests_glibc.nmf");
    obj.setAttribute("type", "application/x-nacl");
    obj.setAttribute("mode", mode);
  } else if (mode == "nacl_pnacl") {
    obj = document.createElement("EMBED");
    obj.setAttribute("src", "ppapi_nacl_tests_pnacl.nmf");
    obj.setAttribute("type", "application/x-nacl");
    obj.setAttribute("mode", mode);
  } else if (mode == "mojo") {
    obj = document.createElement("EMBED");
    obj.setAttribute("src",
                     "test_data/ppapi/tests/mojo/pnacl/ppapi_tests_mojo.nmf");
    obj.setAttribute("type", "application/x-pnacl");
    obj.setAttribute("mode", mode);
  } else {
    var mimeType = "application/x-ppapi-tests";
    if (mimeType in navigator.mimeTypes) {
      obj = document.createElement("EMBED");
      obj.setAttribute("src", "http://a.b.c/test");
      obj.setAttribute("type", mimeType);
    } else {
      document.getElementById("console").innerHTML =
          '<span class="fail">FAIL</span>: ' +
          '<span class="err_msg">Test plugin is not registered.</span>';
    }
  }

  if (obj) {
    obj.setAttribute("width", 80);
    obj.setAttribute("height", 80);
    obj.setAttribute("style",
                     "background-color:#AAAAAA;border:1px solid black;");
    obj.setAttribute("id", "plugin");
    obj.setAttribute("testcase", testcase);
    obj.setAttribute("protocol", window.location.protocol);
    var websocket_host = ExtractSearchParameter("websocket_host");
    if (websocket_host != "")
      obj.setAttribute("websocket_host", websocket_host);
    var websocket_port = ExtractSearchParameter("websocket_port");
    if (websocket_port != "")
      obj.setAttribute("websocket_port", websocket_port);
    var ssl_server_port = ExtractSearchParameter("ssl_server_port");
    if (ssl_server_port != "")
      obj.setAttribute("ssl_server_port", ssl_server_port);

    var container = document.getElementById("container");
    container.addEventListener("message", handleTestingMessage, true);

    // "error" and "crash" events will only fire for NaCl, but adding these
    // listeners doesn't hurt in the non-NaCl cases.
    obj.addEventListener("error", function() {
      InternalError("Plugin did not load. '" + obj.lastError + "'");
    }, true);
    obj.addEventListener("crash", function() {
      InternalError("Plugin crashed. '" + obj.lastError + "'");
    }, true);

    // NaCl sends progress events while loading. When we get one, notify the
    // domAutomationController so that it knows we're still working.
    obj.addEventListener("loadstart", sendProgress, true);
    obj.addEventListener("progress", sendProgress, true);
    obj.addEventListener("load", sendProgress, true);
    obj.addEventListener("loadend", sendProgress, true);

    // Register a bad dispatchEvent to make sure it isn't used. See 'EVIL' note
    // below.
    var original = obj.dispatchEvent;
    obj.dispatchEvent = function() {
      InternalError("Bad dispatchEvent called!");
    }
    container.appendChild(obj);
  }
}

// EVIL Note:
// This part of the script does some nefarious things to make sure that it
// doesn't affect the behavior of PostMessage (on which all the tests rely). In
// particular, we replace document.createEvent, MessageEvent.initMessageEvent,
// and the MessageEvent constructor. Previously, the NaCl integration
// implementation made use of these and would fail (http://crbug.com/82604
// and http://crbug.com/109775).
document.createEvent = function() {
  InternalError("Bad document.createEvent called!");
}
function MessageEvent() {
  InternalError("Bad MessageEvent constructor called!");
}
MessageEvent.prototype.initMessageEvent = function() {
  InternalError("Bad MessageEvent.initMessageEvent called!");
}

</script>
</head><body>
<div>
  <div id="container"></div>
  <div id="console"><span class="load_msg">loading...</span></div>
</div>
</body></html>
